﻿#region Apache License that was here originally
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

#region Additional licensing notices
// This implementation is based on the original AsyncAppender that ships as
// a sample with log4Net. The original file has been changed by Mikhail Dutikov.
#endregion

using System;
using log4net.Appender;
using log4net.Core;
using log4net.Util;

namespace AsyncLoggers.Log4Net
{
    /// <summary>
    /// Appender that forwards LoggingEvents asynchronously
    /// </summary>
    /// <remarks>
    /// This appender forwards LoggingEvents to a list of attached appenders.
    /// The events are forwarded asynchronously and processed strictly in order they were forwarded.
    /// </remarks>
    public sealed class OrderedAsyncAppender : IAppender, IBulkAppender, IOptionHandler, IAppenderAttachable
    {
        private readonly CommandProcessor _commandProcessor = new CommandProcessor();
        private AppenderAttachedImpl _appenderAttached;

        // Note: this property is a performance gamechanger as capturing some things
        // on the calling thread can slow this appender down drastically
        private FixFlags _fixFlags = FixFlags.ThreadName|FixFlags.Message;
        private readonly object _stateChangeCriticalSection = new object();

        /// <summary>
        /// Gets or sets the name of this appender.
        /// </summary>
        /// <value>
        /// The name of the appender.
        /// </value>
        /// <remarks>
        /// <para>
        /// The name uniquely identifies the appender.
        /// </para>
        /// </remarks>
        public string Name { get; set; }

        /// <summary>
        /// Activate the options that were previously set with calls to properties.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This allows an object to defer activation of its options until all
        ///             options have been set. This is required for components which have
        ///             related options that remain ambiguous until all are set.
        /// </para>
        /// <para>
        /// If a component implements this interface then this method must be called
        ///             after its properties have been set before the component can be used.
        /// </para>
        /// </remarks>
        public void ActivateOptions()
        {
        }

        /// <summary>
        /// Specifies what event information should be captured on the thread that's calling into
        /// the logger before the event is passed for asynchronous handling to another thread.
        /// </summary>
        public FixFlags Fix
        {
            get { return _fixFlags; }
            set { _fixFlags = value; }
        }

        /// <summary>
        /// Closes the appender and releases resources.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Releases any resources allocated within the appender such as file handles, 
        ///             network connections, etc.
        /// </para>
        /// <para>
        /// It is a programming error to append to a closed appender.
        /// </para>
        /// </remarks>
        public void Close()
        {
            // Remove all the attached appenders

            lock (_stateChangeCriticalSection)
            {
                if (_appenderAttached != null)
                {
                    _appenderAttached.RemoveAllAppenders();
                }

                _commandProcessor.Dispose();
            }
        }

        /// <summary>
        /// Log the logging event in Appender specific way.
        /// </summary>
        /// <param name="loggingEvent">The event to log</param>
        /// <remarks>
        /// <para>
        /// This method is called to log a message into this appender.
        /// </para>
        /// </remarks>
        public void DoAppend(LoggingEvent loggingEvent)
        {
            if (loggingEvent == null) throw new ArgumentNullException("loggingEvent");

            // Note: this can be slow, and carrying the work of 'fixing volatile data' over to
            // another thread can yield incorrect results
            loggingEvent.Fix = _fixFlags;

            _commandProcessor.QueueCommand(
                new ActionCommand(() =>
                                      {
                                          _appenderAttached.AppendLoopOnAppenders(loggingEvent);
                                      }));
        }

        /// <summary>
        /// Log the array of logging events in Appender specific way.
        /// </summary>
        /// <param name="loggingEvents">The events to log</param>
        /// <remarks>
        /// <para>
        /// This method is called to log an array of events into this appender.
        /// </para>
        /// </remarks>
        public void DoAppend(LoggingEvent[] loggingEvents)
        {
            if (loggingEvents == null) throw new ArgumentNullException("loggingEvents");

            // Note: this can be slow, and carrying the work of 'fixing volatile data' over to
            // another thread can yield incorrect results
            foreach (LoggingEvent loggingEvent in loggingEvents)
            {
                loggingEvent.Fix = _fixFlags;
            }

            _commandProcessor.QueueCommand(
                new ActionCommand(() =>
                                      {
                                          _appenderAttached.AppendLoopOnAppenders(loggingEvents);
                                      }));
        }

        #region IAppenderAttachable Members

        /// <summary>
        /// Attaches an appender.
        /// </summary>
        /// <param name="newAppender">The appender to add.</param>
        /// <remarks>
        /// <para>
        /// Add the specified appender. The implementation may
        ///             choose to allow or deny duplicate appenders.
        /// </para>
        /// </remarks>
        public void AddAppender(IAppender newAppender)
        {
            if (newAppender == null)
            {
                throw new ArgumentNullException("newAppender");
            }

            lock (_stateChangeCriticalSection)
            {
                if (_appenderAttached == null)
                {
                    _appenderAttached = new log4net.Util.AppenderAttachedImpl();
                }
                _appenderAttached.AddAppender(newAppender);
            }
        }

        /// <summary>
        /// Gets all attached appenders.
        /// </summary>
        /// <value>
        /// A collection of attached appenders.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets a collection of attached appenders.
        ///             If there are no attached appenders the
        ///             implementation should return an empty 
        ///             collection rather than <c>null</c>.
        /// </para>
        /// </remarks>
        public AppenderCollection Appenders
        {
            get
            {
                lock (_stateChangeCriticalSection)
                {
                    if (_appenderAttached == null)
                    {
                        return AppenderCollection.EmptyCollection;
                    }
                    else
                    {
                        return _appenderAttached.Appenders;
                    }
                }
            }
        }

        /// <summary>
        /// Gets an attached appender with the specified name.
        /// </summary>
        /// <param name="name">The name of the appender to get.</param>
        /// <returns>
        /// The appender with the name specified, or <c>null</c> if no appender with the
        ///             specified name is found.
        /// </returns>
        /// <remarks>
        /// <para>
        /// Returns an attached appender with the <paramref name="name"/> specified.
        ///             If no appender with the specified name is found <c>null</c> will be
        ///             returned.
        /// </para>
        /// </remarks>
        public IAppender GetAppender(string name)
        {
            lock (_stateChangeCriticalSection)
            {
                if (_appenderAttached == null || name == null)
                {
                    return null;
                }

                return _appenderAttached.GetAppender(name);
            }
        }

        /// <summary>
        /// Removes all attached appenders.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Removes and closes all attached appenders
        /// </para>
        /// </remarks>
        public void RemoveAllAppenders()
        {
            lock (_stateChangeCriticalSection)
            {
                if (_appenderAttached != null)
                {
                    _appenderAttached.RemoveAllAppenders();
                    _appenderAttached = null;
                }
            }
        }

        /// <summary>
        /// Removes the specified appender from the list of attached appenders.
        /// </summary>
        /// <param name="appender">The appender to remove.</param>
        /// <returns>
        /// The appender removed from the list
        /// </returns>
        /// <remarks>
        /// <para>
        /// The appender removed is not closed.
        ///             If you are discarding the appender you must call
        ///             <see cref="M:log4net.Appender.IAppender.Close"/> on the appender removed.
        /// </para>
        /// </remarks>
        public IAppender RemoveAppender(IAppender appender)
        {
            lock (_stateChangeCriticalSection)
            {
                if (appender != null && _appenderAttached != null)
                {
                    return _appenderAttached.RemoveAppender(appender);
                }
            }
            return null;
        }

        /// <summary>
        /// Removes the appender with the specified name from the list of appenders.
        /// </summary>
        /// <param name="name">The name of the appender to remove.</param>
        /// <returns>
        /// The appender removed from the list
        /// </returns>
        /// <remarks>
        /// <para>
        /// The appender removed is not closed.
        ///             If you are discarding the appender you must call
        ///             <see cref="M:log4net.Appender.IAppender.Close"/> on the appender removed.
        /// </para>
        /// </remarks>
        public IAppender RemoveAppender(string name)
        {
            lock (_stateChangeCriticalSection)
            {
                if (name != null && _appenderAttached != null)
                {
                    return _appenderAttached.RemoveAppender(name);
                }
            }

            return null;
        }

        #endregion
    }
}